/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.folderit.apps.rooms;

import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.test.AndroidTestCase;
import android.util.Log;

import com.folderit.apps.rooms.data.RoomsContract.ReservationEntry;
import com.folderit.apps.rooms.data.RoomsContract.RoomEntry;
import com.folderit.apps.rooms.data.RoomsContract.UserEntry;

public class TestProvider extends AndroidTestCase {

    public static final String LOG_TAG = TestProvider.class.getSimpleName();

    // brings our database to an empty state
    public void deleteAllRecords() {
        mContext.getContentResolver().delete(
                ReservationEntry.CONTENT_URI,
                null,
                null
        );
        mContext.getContentResolver().delete(
                RoomEntry.CONTENT_URI,
                null,
                null
        );
        mContext.getContentResolver().delete(
                UserEntry.CONTENT_URI,
                null,
                null
        );

        Cursor cursor = mContext.getContentResolver().query(
                ReservationEntry.CONTENT_URI,
                null,
                null,
                null,
                null
        );
        assertEquals(0, cursor.getCount());
        cursor.close();

        cursor = mContext.getContentResolver().query(
                RoomEntry.CONTENT_URI,
                null,
                null,
                null,
                null
        );
        assertEquals(0, cursor.getCount());
        cursor.close();

        cursor = mContext.getContentResolver().query(
                UserEntry.CONTENT_URI,
                null,
                null,
                null,
                null
        );
        assertEquals(0, cursor.getCount());
        cursor.close();
    }

    // Since we want each test to start with a clean slate, run deleteAllRecords
    // in setUp (called by the test runner before each test).
    public void setUp() {
        deleteAllRecords();
    }

    public void testInsertReadProvider() {

        /** Tests for User **/
        ContentValues testValues = TestDb.createUserValues();

        Uri userUri = mContext.getContentResolver().insert(UserEntry.CONTENT_URI, testValues);
        long userRowId = ContentUris.parseId(userUri);

        // Verify we got a row back.
        assertTrue(userRowId != -1);

        // Data's inserted.  IN THEORY.  Now pull some out to stare at it and verify it made
        // the round trip.

        // A cursor is your primary interface to the query results.
        Cursor cursor = mContext.getContentResolver().query(
                UserEntry.CONTENT_URI,
                null, // leaving "columns" null just returns all the columns.
                null, // cols for "where" clause
                null, // values for "where" clause
                null  // sort order
        );

        TestDb.validateCursor(cursor, testValues);

        // Now see if we can successfully query if we include the row id
        cursor = mContext.getContentResolver().query(
                UserEntry.buildUserUri(userRowId),
                null, // leaving "columns" null just returns all the columns.
                null, // cols for "where" clause
                null, // values for "where" clause
                null  // sort order
        );

        TestDb.validateCursor(cursor, testValues);

        /** Tests for Rooms **/
        ContentValues roomTestValues = TestDb.createRoomValues();

        Uri roomUri = mContext.getContentResolver().insert(RoomEntry.CONTENT_URI, roomTestValues);
        long roomRowId = ContentUris.parseId(roomUri);

        // Verify we got a row back.
        assertTrue(roomRowId != -1);

        // Data's inserted.  IN THEORY.  Now pull some out to stare at it and verify it made
        // the round trip.

        // A cursor is your primary interface to the query results.
        Cursor roomCursor = mContext.getContentResolver().query(
                RoomEntry.CONTENT_URI,
                null, // leaving "columns" null just returns all the columns.
                null, // cols for "where" clause
                null, // values for "where" clause
                null  // sort order
        );

        TestDb.validateCursor(roomCursor, roomTestValues);

        // Now see if we can successfully query if we include the row id
        roomCursor = mContext.getContentResolver().query(
                RoomEntry.buildRoomUri(roomRowId),
                null, // leaving "columns" null just returns all the columns.
                null, // cols for "where" clause
                null, // values for "where" clause
                null  // sort order
        );

        TestDb.validateCursor(roomCursor, roomTestValues);

        /** Reservation Tests **/
        ContentValues reservationValues = TestDb.createReservationValues(userRowId, roomRowId);

        Uri reservationInsertUri = mContext.getContentResolver()
                .insert(ReservationEntry.CONTENT_URI, reservationValues);
        assertTrue(reservationInsertUri != null);

        long reservationId = ContentUris.parseId(reservationInsertUri);
        assertTrue(reservationId != -1);

        // A cursor is your primary interface to the query results.
        Cursor reservationCursor = mContext.getContentResolver().query(
                ReservationEntry.CONTENT_URI,  // Table to Query
                null, // leaving "columns" null just returns all the columns.
                null, // cols for "where" clause
                null, // values for "where" clause
                null // columns to group by
        );

        TestDb.validateCursor(reservationCursor, reservationValues);

        reservationCursor = mContext.getContentResolver().query(
                ReservationEntry.buildReservationUri(reservationId),  // Table to Query
                null, // leaving "columns" null just returns all the columns.
                null, // cols for "where" clause
                null, // values for "where" clause
                null // columns to group by
        );

        TestDb.validateCursor(reservationCursor, reservationValues);

        // Put all values together to ensure that the join
        // worked and we actually get all the values back
        addAllContentValues(reservationValues, testValues);
        addAllContentValues(reservationValues, roomTestValues);

        // Get the joined Reservations with rooms and users
        reservationCursor = mContext.getContentResolver().query(
                ReservationEntry.buildReservationWithRoomAndDateRange(
                        roomRowId,
                        "20141201000000", // Dec 01 2014 00:00:00
                        "20141231235959"), // Dec 31 2014 23:59:59
                null, // leaving "columns" null just returns all the columns.
                null, // cols for "where" clause
                null, // values for "where" clause
                null  // sort order
        );
        TestDb.validateCursor(reservationCursor, reservationValues);

        // TODO: Add more tests for different date ranges.
    }

    public void testGetType() {
        // content://com.folderit.apps.rooms/reservation/
        String type = mContext.getContentResolver().getType(ReservationEntry.CONTENT_URI);
        // vnd.android.cursor.dir/com.folderit.apps.rooms/reservation
        assertEquals(ReservationEntry.CONTENT_TYPE, type);

        long testReservationId = 1234;
        // content://com.folderit.apps.rooms/reservation/1234
        type = mContext.getContentResolver().getType(
                ReservationEntry.buildReservationUri(testReservationId));
        // vnd.android.cursor.item/com.folderit.apps.rooms/reservation
        assertEquals(ReservationEntry.CONTENT_ITEM_TYPE, type);

        String testFrom = "20141201000000";
        String testTo = "20141231235959";
        // content://com.folderit.apps.rooms/reservation/1234/20141201000000/20141231235959
        type = mContext.getContentResolver().getType(
                ReservationEntry.
                        buildReservationWithRoomAndDateRange(testReservationId, testFrom, testTo));
        // vnd.android.cursor.dir/com.folderit.apps.rooms/reservation
        assertEquals(ReservationEntry.CONTENT_TYPE, type);

        // content://com.folderit.apps.rooms/room/
        type = mContext.getContentResolver().getType(RoomEntry.CONTENT_URI);
        // vnd.android.cursor.dir/com.folderit.apps.rooms/room
        assertEquals(RoomEntry.CONTENT_TYPE, type);

        // content://com.folderit.apps.rooms/room/1
        type = mContext.getContentResolver().getType(RoomEntry.buildRoomUri(1L));
        // vnd.android.cursor.item/com.folderit.apps.rooms/room
        assertEquals(RoomEntry.CONTENT_ITEM_TYPE, type);

        // content://com.folderit.apps.rooms/user/
        type = mContext.getContentResolver().getType(UserEntry.CONTENT_URI);
        // vnd.android.cursor.dir/com.folderit.apps.rooms/user
        assertEquals(UserEntry.CONTENT_TYPE, type);

        // content://com.folderit.apps.rooms/user/1
        type = mContext.getContentResolver().getType(UserEntry.buildUserUri(1L));
        // vnd.android.cursor.item/com.folderit.apps.rooms/user
        assertEquals(UserEntry.CONTENT_ITEM_TYPE, type);
    }

    public void testUpdateUser() {
        // Create a new map of values, where column names are the keys
        ContentValues values = TestDb.createUserValues();

        Uri userUri = mContext.getContentResolver().
                insert(UserEntry.CONTENT_URI, values);
        long userRowId = ContentUris.parseId(userUri);

        // Verify we got a row back.
        assertTrue(userRowId != -1);
        Log.d(LOG_TAG, "New row id: " + userRowId);

        ContentValues updatedValues = new ContentValues(values);
        updatedValues.put(UserEntry._ID, userRowId);
        updatedValues.put(UserEntry.COLUMN_NAME, "César Sandrigo");

        int count = mContext.getContentResolver().update(
                UserEntry.CONTENT_URI, updatedValues, UserEntry._ID + "= ?",
                new String[] { Long.toString(userRowId)});

        assertEquals(count, 1);

        // A cursor is your primary interface to the query results.
        Cursor cursor = mContext.getContentResolver().query(
                UserEntry.buildUserUri(userRowId),
                null,
                null, // Columns for the "where" clause
                null, // Values for the "where" clause
                null // sort order
        );

        TestDb.validateCursor(cursor, updatedValues);
    }

    public void testUpdateRoom() {
        // Create a new map of values, where column names are the keys
        ContentValues values = TestDb.createRoomValues();

        Uri roomUri = mContext.getContentResolver().
                insert(RoomEntry.CONTENT_URI, values);
        long roomRowId = ContentUris.parseId(roomUri);

        // Verify we got a row back.
        assertTrue(roomRowId != -1);
        Log.d(LOG_TAG, "New row id: " + roomRowId);

        ContentValues updatedValues = new ContentValues(values);
        updatedValues.put(RoomEntry._ID, roomRowId);
        updatedValues.put(RoomEntry.COLUMN_ROOM_NAME, "Secret Room Underground (AKA Bunker)");

        int count = mContext.getContentResolver().update(
                RoomEntry.CONTENT_URI, updatedValues, RoomEntry._ID + "= ?",
                new String[] { Long.toString(roomRowId)});

        assertEquals(count, 1);

        // A cursor is your primary interface to the query results.
        Cursor cursor = mContext.getContentResolver().query(
                RoomEntry.buildRoomUri(roomRowId),
                null,
                null, // Columns for the "where" clause
                null, // Values for the "where" clause
                null // sort order
        );

        TestDb.validateCursor(cursor, updatedValues);
    }

    // Make sure we can still delete after adding/updating stuff
    public void testDeleteRecordsAtEnd() {
        deleteAllRecords();
    }

    // The target api annotation is needed for the call to keySet -- we wouldn't want
    // to use this in our app, but in a test it's fine to assume a higher target.
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    void addAllContentValues(ContentValues destination, ContentValues source) {
        for (String key : source.keySet()) {
            destination.put(key, source.getAsString(key));
        }
    }

    static ContentValues createRoomExampleValues() {
        ContentValues roomValues = new ContentValues();
        roomValues.put(RoomEntry.COLUMN_ROOM_NAME, "One-person Room");
        roomValues.put(RoomEntry.COLUMN_DESCRIPTION, "A very very small room");
        roomValues.put(RoomEntry.COLUMN_COLOR_ID, 2);
        roomValues.put(RoomEntry.COLUMN_EXTERNAL_ID, 1234);

        return roomValues;
    }

    static ContentValues createUserExampleValues() {
        ContentValues userValues = new ContentValues();
        userValues.put(UserEntry.COLUMN_NAME, "Homer J. Simpson");
        userValues.put(UserEntry.COLUMN_PHONE_NUMBER, "+1444123456");
        userValues.put(UserEntry.COLUMN_EMAIL, "hjs@domain.com");

        return userValues;
    }

    static ContentValues createReservationExampleValues(long roomId, long userId) {
        ContentValues reservationValues = new ContentValues();
        reservationValues.put(ReservationEntry.COLUMN_ROOM_KEY, roomId);
        reservationValues.put(ReservationEntry.COLUMN_USER_KEY, userId);
        reservationValues.put(ReservationEntry.COLUMN_CREATED, "01012015000001");
        reservationValues.put(ReservationEntry.COLUMN_BEGIN, "02012015120000");
        reservationValues.put(ReservationEntry.COLUMN_END, "02012015144500");
        reservationValues.put(ReservationEntry.COLUMN_EXTERNAL_ID, 123);
        reservationValues.put(ReservationEntry.COLUMN_EVENT_TITLE, "Just another meeting");

        return reservationValues;
    }

    private long roomRowId;
    private long userRowId;

    // Inserts the example data.
    public void insertExampleData() {
        Uri roomUri = mContext.getContentResolver()
                .insert(RoomEntry.CONTENT_URI, createRoomExampleValues());
        assertTrue(roomUri != null);

        roomRowId = ContentUris.parseId(roomUri);

        Uri userUri = mContext.getContentResolver()
                .insert(UserEntry.CONTENT_URI, createUserExampleValues());
        assertTrue(userUri != null);

        userRowId = ContentUris.parseId(userUri);

        ContentValues reservationValues = createReservationExampleValues(roomRowId, userRowId);
        Uri reservationUri = mContext.getContentResolver()
                .insert(ReservationEntry.CONTENT_URI, reservationValues);
        assertTrue(reservationUri != null);
    }

    public void testUpdateAndReadReservation() {
        insertExampleData();
        String newEndTime = "02012015154500"; // One more hour

        // Make an update to one value.
        ContentValues reservationExampleUpdate = new ContentValues();
        reservationExampleUpdate.put(ReservationEntry.COLUMN_END, newEndTime);

        mContext.getContentResolver().update(
                ReservationEntry.CONTENT_URI, reservationExampleUpdate, null, null);

        // A cursor is your primary interface to the query results.
        Cursor reservationCursor = mContext.getContentResolver().query(
                ReservationEntry.CONTENT_URI,
                null,
                null,
                null,
                null
        );

        // Make the same update to the full ContentValues for comparison.
        ContentValues reservationExampleAltered = createReservationExampleValues(roomRowId, userRowId);
        reservationExampleAltered.put(ReservationEntry.COLUMN_END, newEndTime);

        TestDb.validateCursor(reservationCursor, reservationExampleAltered);
    }
}
